package org.michael.u.brg.by.spring.aop.abcthreads;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.util.concurrent.CountDownLatch;

/**
 * Spawns several C runners showcasing traditional Java wait/notify locking, after all Cs are done,
 * spawns A again.
 */
public class RunnerB implements Runnable, ApplicationContextAware {

    /**
     * We need app context to get beans.
     */
	private ApplicationContext springAppcontext;

    /**
     * Concurrency aid to wait for all C runners to finish before spawning the A runner.
     */
    private CountDownLatch cRunnersDone;

    /**
     * Application startup time.
     */
    private long startupMillis;

    /**
     * wait/notify mutex for the C runners.
     */
	private final Object mutexC = new Object();
	
	private Long id;
	private long delay;

    /**
     * How many C runners to spawn.
     */
	private int countOfCs;

	private void spawnCs(int ordinal) {
		try { 
			RunnerC c = (RunnerC) springAppcontext.getBean("c");
			c.setGuide(this);
			Thread tc = new Thread(c, String.format("C#%d [%d]", c.getId(), ordinal));
			System.out.printf("%n   B-%d - starting by C#%d", id, c.getId());
			tc.start();
			synchronized(mutexC) { // grab the lock
				
				System.out.printf("%n   B-%d - pre-wait on C#%d[%d] (%s)", id, c.getId(), ordinal, Thread.currentThread().getName());
				mutexC.wait(); // give up the lock and sleep
				// the thread only can leave wait() by reacquiring the lock on the mutex, see the javadocs.
				System.out.printf("%n   B-%d - post-wait on C#%d[%d] (%s)", id, c.getId(), ordinal, Thread.currentThread().getName());
			}
		} 
		catch(InterruptedException x) {
			x.printStackTrace();
		}
	}

	public void run() {

		System.out.printf("%n   B-%d: start at %,d", id, System.currentTimeMillis() - startupMillis);
		
		try { 
			Thread.sleep(delay);
			for(int i = 1; i <= countOfCs; i++) { // after delay, spawns several C runners.
				final int newOrdinal = i; // closures in Java: the var must be final.
				new Thread(new Runnable() {

					public void run() {
						spawnCs(newOrdinal);
					}
					
				}, String.format("   B-%d, spawned by C[%d]", id, newOrdinal)).start();
			}
            cRunnersDone.await(); // wait till all C runners are done
		} 
		catch(InterruptedException x) {
			x.printStackTrace();
		}
		
		RunnerA a = (RunnerA) springAppcontext.getBean("a");
		System.out.printf("%n   B-%d: spawned A#%d at %,d", id, a.getRunnerId(), System.currentTimeMillis() - startupMillis);
		a.start();

		System.out.printf("%n   B-%d: ended at %,d", id, System.currentTimeMillis() - startupMillis);
	}

	public void setIdGetter(IdProvider id) {
		this.id = id.getNext();
	}

	public void setDelay(Long delay) {
		this.delay = delay;
	}

    /**
     * Releases one thread waiting on the {@link #mutexC}
     */
	public void releaseC() {
		System.out.printf("%n   B-%d - pre-notify (%s)", id, Thread.currentThread().getName());
		synchronized(mutexC) {
			mutexC.notify();
            cRunnersDone.countDown();
		}
		System.out.printf("%n   B-%d - post-notify (%s), latch count: %d", id, Thread.currentThread().getName()
            , cRunnersDone.getCount());
	}
	
	public void setApplicationContext(ApplicationContext value)
			throws BeansException {
		springAppcontext = value;
		
	}

	public Long getId() {
		return id;
	}

	public void setCountOfCs(int cCount) {
		this.countOfCs= cCount;
	}
	
	public void setStartupMillis(long startupMillis) {
		this.startupMillis = startupMillis;
	}
    
    public void init() {
        cRunnersDone = new CountDownLatch(countOfCs);
    }
}
